////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNCore/inc/WNBase.h"
#include "WNCore/inc/WNFunctions.h"
#include "WNMemory/inc/WNMemory.h"
#include "WNStrings/inc/WNStrings.h"
#include "WNScripting/inc/WNScriptLexerDefines.h"
#include "WNScripting/inc/WNScriptingEngine.h"
#include "WNScripting/inc/WNScriptingEngineData.h"

#include <string>

#define YYCTYPE     WN_CHAR
#define YYCURSOR    (s->mStart)
#define YYLIMIT     (s->mEnd)
#define YYMARKER    (s->mMarker)

enum WNQuickLexState {
    eWNQuickLexTop,
    eWNQuickLexFunction,
    eWNQuickLexInFunction,
    eWNQuickLexStruct,
    eWNQuickLexDone
};

WN_VOID WNCountWhitespaceAndLineNo(WN_CHAR* startChar, WN_CHAR* endChar, WN_SIZE_T& lineNo, WN_SIZE_T& charNo) {
    WN_RELEASE_ASSERT(endChar >= startChar);

    while (startChar <= endChar) {
        switch(*startChar) {
            case '\n':
                lineNo += 1;
                charNo = 0;

                break;
            case '\t':
                charNo += WN_SCRIPTING_TAB_SIZE;

                break;
            default:
                charNo += 1;
        }

        startChar++;
    }
}

WN_INT32 WNQuickLex(WNLexerInfo* s, WNString& errormsg, std::list<WNScriptDefinedElement*>& scriptAccessibleItems, WNScriptingEngineData& data) {
    WNQuickLexState lex = eWNQuickLexTop;
    WN_INT32 numBraces = 0;
    WN_INT32 r = 0;
    WN_BOOL inStruct = WN_FALSE;
    WN_CHAR* tempStart = s->mStart;
    WN_CHAR* start = s->mStart;
    WN_BOOL consumeWhitespace = WN_TRUE;
    WNScriptDefinedElement* curItem = WN_NULL;
    WNTypeElement mCurStruct = WN_NULL;

    while (WN_TRUE) {
        switch (lex) {
            case eWNQuickLexTop:
            {
                /*!re2c
                    re2c:indent:top = 1;
                    re2c:indent:string = "  ";
                    re2c:yyfill:enable = 0;

                    "#include" ("\t"|"\n"|"\r"|" ")+ "\"" [^\"]+ "\""{
                        if(numBraces != 0){
                            errormsg = "Unexpected place to find #include directive";
                            return(-1);
                        }

                        curItem = WN_NEW(WNScriptDefinedElement);
                        curItem->mType = eWNScriptInclude;
                        curItem->mFileName = s->mFilename;
                        curItem->mOffset = start - s->mFileStart;
                        
                        WN_CHAR* endpos = start;

                        endpos += WNStrLen("#include");
                        for(;*endpos == '\t' || *endpos == '\n' || *endpos == '\r' || *endpos == ' '; ++endpos); //skip whitespace
                        ++endpos; //skip the leading "
                        WN_CHAR* endstring = endpos;
                        for(;*endstring != '\"'; ++endstring);
                        curItem->mIncludeName = s->mIdentifierMap->GetIdentifier(endpos, (endstring - endpos));
                        scriptAccessibleItems.push_back(curItem);
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);
                        start = YYCURSOR;
                        continue;
                    }

                    "struct" ("\t"|"\n"|"\r"|" ")+  [A-Z][a-zA-Z0-9_\-]* {
                        if (numBraces != 0) { //We cannot define a struct in a non-top scope block
                            errormsg = "Unexpected struct found (inside a scope block)";
                            return(-1);
                        }

                        curItem = WN_NEW(WNScriptDefinedElement);
                        curItem->mType = eWNScriptStruct;
                        curItem->mFileName = s->mFilename;
                        curItem->mOffset = start - s->mFileStart;

                        WN_CHAR* endpos = start;

                        endpos += WNStrLen("struct");

                        for(;*endpos == '\t' || *endpos == '\n' || *endpos == '\r' || *endpos == ' '; ++endpos); //skip whitespace

                        WN_CHAR* startname = endpos;

                        for(;*endpos != '\t' && *endpos != '\n' && *endpos != '\r' && *endpos != ' ' && *endpos != '{'; ++endpos);

                        curItem->mGlobalIdentifier = s->mIdentifierMap->GetIdentifier(startname, endpos - startname);

                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces = 0;
                        lex = eWNQuickLexStruct;
                        mCurStruct = data.mTypeManager->RegisterStructType(curItem->mGlobalIdentifier);

                        continue;
                    }

                    "stackret "?[A-Z][a-zA-Z0-9_\-]*(("\t"|"\n"|"\r"|" ")*"\["("\t"|"\n"|"\r"|" ")*"]")*("\t"|"\n"|"\r"|" ")+"function" ("\t"|"\n"|"\r"|" ")+ [a-zA-Z][a-zA-Z0-9_\-]*("\t"|"\n"|"\r"|" ")* {
                        if (numBraces != 0) { // We are in a function... and found function
                            errormsg = "Unexpected function found\n";
                            return(-1);
                        }

                        curItem = WN_NEW(WNScriptDefinedElement);
                        curItem->mType = eWNScriptFunction;
                        curItem->mFileName =  s->mFilename;
                        curItem->mOffset = start - s->mFileStart;

                        WN_CHAR* endpos = start;

                        const char* stackret = "stackret ";
                        bool onStack = true;
                        for(size_t i = 0; i < 9; ++i){
                            if(endpos[i] != stackret[i]){
                                onStack = false;
                                break;
                            }
                        }

                        if(onStack){
                            endpos += 9;
                        }
                        curItem->mOnStack = onStack;
                        start = endpos;
                        for(;*endpos != '\t' && *endpos != '\n' && *endpos != '\r' && *endpos != ' ' && *endpos != ']' && *endpos != '['; ++endpos); // go to end of whitespace, this is the typename
                        curItem->mReturnType = s->mTypeManager->GetUserTypeByName(s->mIdentifierMap->GetIdentifier(start, (endpos - start)));
                        if(!WNIsValid(curItem->mReturnType)){
                            errormsg = "Bad return type";
                            return(-1);
                        }
                        WN_BOOL inBrackets = false;
                        WN_INT32 numBrackets = 0;

                        while( true ){
                            if(*endpos == '\t' || *endpos == ' ' || *endpos == '\r' || *endpos == '\n'){
                                ++endpos;
                            } else if (*endpos == '['){
                                if(inBrackets){ errormsg = "Bad array defition in function header\n";return(-1); }
                                inBrackets = true;
                                numBrackets += 1;
                                ++endpos;
                            } else if (*endpos == ']'){
                                if(!inBrackets) { errormsg = "Bad array defition in function header\n"; return(-1); }
                                inBrackets = false;
                                ++endpos;
                            } else {
                                break;
                            }
                        }
                        if (inBrackets){
                            errormsg = "Bad array defition in function header\n"; 
                            return(-1);
                        }
                        
                        for(int i = 0; i < numBrackets; ++i){
                            curItem->mReturnType = s->mTypeManager->GetArrayType(curItem->mReturnType);
                        }

                        endpos += WNStrLen("function");

                        for(;*endpos == '\t' || *endpos == ' ' || *endpos == '\r' || *endpos == '\n'; ++endpos); //go to the end of the whitespace, this is our identifier name

                        WN_CHAR* startname = endpos;

                        for(;*endpos != '\t' && *endpos != '\n' && *endpos != '\r' && *endpos != ' ' && *endpos != '('; ++endpos); 
                                        
                        curItem->mGlobalIdentifier =  s->mIdentifierMap->GetIdentifier(startname, endpos - startname);
                        lex = eWNQuickLexFunction;

                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces = 0;

                        continue;
                    }

                    [\000] {
                        return(0);
                    }

                    "{" {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces += 1;

                        continue;
                    }

                    "}" {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces -= 1;

                        continue;
                    }

                    [^] {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;

                        continue;
                    }
                */
                break;
            }
            case eWNQuickLexFunction:
            {
                 /*!re2c
                    re2c:indent:top = 1;
                    re2c:indent:string = "  ";
                    re2c:yyfill:enable = 0;

                    "(" {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces += 1;

                        continue;
                    }

                    ")" {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;
                        numBraces -= 1;

                        if (numBraces > 0) {
                            return(-1);
                        }

                        lex = eWNQuickLexInFunction;

                        continue;
                    }

                    ("\t"|"\n"|"\r"|" ")* {
                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;

                        continue;
                    }

                    ("\t"|"\n"|"\r"|" ")*[A-Z][a-zA-Z0-9_\-]*(("\t"|"\n"|"\r"|" ")*"\["("\t"|"\n"|"\r"|" ")*"]")*("\t"|"\n"|"\r"|" ")+[a-zA-Z][a-zA-Z0-9_\-]*("\t"|"\n"|"\r"|" ")*","? {
                        if (numBraces != 1) {
                            errormsg = "Unexpected number of braces in function header";
                            return(-1);
                        }

                        WN_CHAR* startpos = start;

                        for(;*startpos == ' ' || *startpos == '\t' || *startpos == '\n' && *startpos == '\r'; ++startpos);

                        WN_CHAR* endpos = startpos;

                        for(;*endpos != ' ' && *endpos != '\t' && *endpos != '\n' && *endpos != '\r' && *endpos != ')' && *endpos != '[' && *endpos != ']'; ++endpos);

                        WNTypeElement argType = s->mTypeManager->GetUserTypeByName(s->mIdentifierMap->GetIdentifier(startpos, (endpos - startpos)));

                        WN_BOOL inBrackets = false;
                        WN_INT32 numBrackets = 0;
                        while( true ){
                            if(*endpos == '\t' || *endpos == ' ' || *endpos == '\r' || *endpos == '\n'){
                                ++endpos;
                            } else if (*endpos == '['){
                                if(inBrackets){ errormsg = "Bad array defition in function header\n"; return(-1); }
                                inBrackets = true;
                                numBrackets += 1;
                                ++endpos;
                            } else if (*endpos == ']'){
                                if(!inBrackets) { errormsg = "Bad array defition in function header\n"; return(-1); }
                                inBrackets = false;
                                ++endpos;
                            } else {
                                break;
                            }
                        }
                        if (inBrackets){
                            errormsg = "Bad array defition in function header\n"; 
                            return(-1);
                        }
                        for(int i = 0; i < numBrackets; ++i){
                            argType = s->mTypeManager->GetArrayType(argType);
                        }

                        curItem->mArguments.push_back(argType);

                        WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                        start = YYCURSOR;

                        continue;
                    }

                    [\000] {
                        errormsg = "Unexpected end of File\n";

                        return(-1);
                    }

                    [^] {
                        errormsg = "Invalid expression in function parameters\n";

                        return(-1);
                    }
                */
                break;
            }
            case eWNQuickLexInFunction:
            {
            /*!re2c
                re2c:indent:top = 1;
                re2c:indent:string = "  ";
                re2c:yyfill:enable = 0;
                
                "{" {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);
                    start = YYCURSOR;
                    numBraces += 1;

                    continue;
                }

                "}" {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                    numBraces -= 1;
                    start = YYCURSOR;

                    if (numBraces == 0) {
                        curItem->mEndOffset = YYCURSOR - tempStart;
                        scriptAccessibleItems.push_back(curItem);
                        lex = eWNQuickLexTop;
                    }

                    continue;
                }

                [^] {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                    start = YYCURSOR;

                    continue;
                }

                [\000] {
                    errormsg = "Unexpected end of File\n";
                    return(-1);
                }
            */
                break;
            }
            case eWNQuickLexStruct:
                /*!re2c
                re2c:indent:top = 1;
                re2c:indent:string = "  ";
                re2c:yyfill:enable = 0;
                
                 "{" {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);
                    start = YYCURSOR;
                    numBraces += 1; 
                    continue;
                 }

                 "}" {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                    numBraces -= 1;
                    start = YYCURSOR;

                    if (numBraces == 0) {
                        curItem->mEndOffset = YYCURSOR - tempStart;
                        scriptAccessibleItems.push_back(curItem);
                        data.mTypeManager->AddStructBody(mCurStruct, curItem->mArguments, curItem->mArgumentNames);
                        
                        lex = eWNQuickLexTop;
                    }

                    continue;
                 }

                 ("\t"|"\n"|"\r"|" ")* {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                    start = YYCURSOR;

                    continue;
                 }

                 [A-Z][a-zA-Z0-9_\-]*(("\t"|"\n"|"\r"|" ")*"\["("\t"|"\n"|"\r"|" ")*"]")*("\t"|"\n"|"\r"|" ")+[a-z][a-zA-Z0-9_\-]* ("\t"|"\n"|"\r"|" ")*";" {
                    WN_CHAR* startpos = start;

                    for (;*startpos != ' ' && *startpos != '\t' && *startpos != '\n' && *startpos != '\r' && *startpos != '[' && *startpos != ']'; ++startpos);

                    WNTypeElement argType = s->mTypeManager->GetUserTypeByName(s->mIdentifierMap->GetIdentifier(start, (startpos - start)));

                    WN_BOOL inBrackets = false;
                    WN_INT32 numBrackets = 0;
                    while( true ){
                        if(*startpos == '\t' || *startpos == ' ' || *startpos == '\r' || *startpos == '\n'){
                            ++startpos;
                        } else if (*startpos == '['){
                            if(inBrackets){ errormsg = "Bad array defition in function header\n"; return(-1); }
                            inBrackets = true;
                            numBrackets += 1;
                            ++startpos;
                        } else if (*startpos == ']'){
                            if(!inBrackets) { errormsg = "Bad array defition in function header\n"; return(-1); }
                            inBrackets = false;
                            ++startpos;
                        } else {
                            break;
                        }
                    }
                    if (inBrackets){
                        errormsg = "Bad array defition in function header\n"; 
                        return(-1);
                    }
                    for(int i = 0; i < numBrackets; ++i){
                        argType = s->mTypeManager->GetArrayType(argType);
                    }

                    curItem->mArguments.push_back(argType);
                    for(;*startpos == ' ' || *startpos == '\t' || *startpos == '\n' || *startpos == '\r'; ++startpos);

                    WN_CHAR* endpos = startpos;

                    for (;*endpos != ' ' && *endpos != '\t' && *endpos != '\n' && *endpos != '\r' && *endpos != ')' && *endpos != ';'; ++endpos);

                    curItem->mArgumentNames.push_back(s->mIdentifierMap->GetIdentifier(startpos, (endpos - startpos)));

                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);

                    start = YYCURSOR;

                    continue;
                 }

                 [^] {
                    WNCountWhitespaceAndLineNo(start, YYCURSOR, s->mLineNo, s->mCharNo);
                    start = YYCURSOR;
                    continue;
                 }
                 */
                break;
            case eWNQuickLexDone:
                return(0);
        }
    }

    return(r);
}